module compression #(
    parameter PADDING_WIDTH = 1024
) (
    input  clk,
    input  rst_n,
    input  start,
    input  [PADDING_WIDTH-1:0] padding_data,
    output [255:0] sha_data,
    output finish
);

    localparam [32*8-1:0] H = {
        32'h6a09e667,
        32'hbb67ae85,
        32'h3c6ef372,
        32'ha54ff53a,
        32'h510e527f,
        32'h9b05688c,
        32'h1f83d9ab,
        32'h5be0cd19
    };

    localparam [32*64-1:0] K = {
        32'h428a2f98, 32'h71374491, 32'hb5c0fbcf, 32'he9b5dba5, 32'h3956c25b, 32'h59f111f1, 32'h923f82a4, 32'hab1c5ed5,
        32'hd807aa98, 32'h12835b01, 32'h243185be, 32'h550c7dc3, 32'h72be5d74, 32'h80deb1fe, 32'h9bdc06a7, 32'hc19bf174,
        32'he49b69c1, 32'hefbe4786, 32'h0fc19dc6, 32'h240ca1cc, 32'h2de92c6f, 32'h4a7484aa, 32'h5cb0a9dc, 32'h76f988da,
        32'h983e5152, 32'ha831c66d, 32'hb00327c8, 32'hbf597fc7, 32'hc6e00bf3, 32'hd5a79147, 32'h06ca6351, 32'h14292967,
        32'h27b70a85, 32'h2e1b2138, 32'h4d2c6dfc, 32'h53380d13, 32'h650a7354, 32'h766a0abb, 32'h81c2c92e, 32'h92722c85,
        32'ha2bfe8a1, 32'ha81a664b, 32'hc24b8b70, 32'hc76c51a3, 32'hd192e819, 32'hd6990624, 32'hf40e3585, 32'h106aa070,
        32'h19a4c116, 32'h1e376c08, 32'h2748774c, 32'h34b0bcb5, 32'h391c0cb3, 32'h4ed8aa4a, 32'h5b9cca4f, 32'h682e6ff3,
        32'h748f82ee, 32'h78a5636f, 32'h84c87814, 32'h8cc70208, 32'h90befffa, 32'ha4506ceb, 32'hbef9a3f7, 32'hc67178f2
    };

    localparam BLOCK_COUNT = PADDING_WIDTH / 512;

    wire working_trig;
    trigger u_trigger (
        .clk(clk),
        .rst_n(rst_n),
        .start(start),
        .trg(working_trig)
    );

    reg [63:0] block_couter_reg;
    reg [63:0] block_couter_next;

    reg [6:0]  cal_md_counter_reg;
    reg [6:0]  cal_md_counter_next;

    wire [511:0] block_data;
    assign block_data = padding_data[512*(BLOCK_COUNT - 1 - block_couter_reg)+511 -: 512];

    reg [31:0] a_reg;
    reg [31:0] a_next;
    reg [31:0] b_reg;
    reg [31:0] b_next;
    reg [31:0] c_reg;
    reg [31:0] c_next;
    reg [31:0] d_reg;
    reg [31:0] d_next;
    reg [31:0] e_reg;
    reg [31:0] e_next;
    reg [31:0] f_reg;
    reg [31:0] f_next;
    reg [31:0] g_reg;
    reg [31:0] g_next;
    reg [31:0] h_reg;
    reg [31:0] h_next;

    reg [31:0] w_reg [0:63];
    reg [31:0] w_next [0:63];
    reg [31:0] md_reg [0:7];
    reg [31:0] md_next [0:7];

    reg finish_reg;
    reg finish_next;

    reg working_reg;
    reg working_next;

    always @(posedge clk or negedge rst_n) begin : update_next
        integer i;
        if (!rst_n) begin

            block_couter_reg <= 0;
            cal_md_counter_reg <= 0;
            
            a_reg <= 0;
            b_reg <= 0;
            c_reg <= 0;
            d_reg <= 0;
            e_reg <= 0;
            f_reg <= 0;
            g_reg <= 0;
            h_reg <= 0;

            for (i = 0; i < 64; i = i + 1) begin
                w_reg[i] <= 0;
            end

            for (i = 0; i < 8; i = i + 1) begin
                md_reg[i] <= 0;
            end

            finish_reg <= 0;
            working_reg <= 0;
        end
        else begin
            block_couter_reg <= block_couter_next;
            cal_md_counter_reg <= cal_md_counter_next;
            
            a_reg <= a_next;
            b_reg <= b_next;
            c_reg <= c_next;
            d_reg <= d_next;
            e_reg <= e_next;
            f_reg <= f_next;
            g_reg <= g_next;
            h_reg <= h_next;

            for (i = 0; i < 64; i = i + 1) begin
                w_reg[i] <= w_next[i];
            end

            for (i = 0; i < 8; i = i + 1) begin
                md_reg[i] <= md_next[i];
            end

            finish_reg <= finish_next;
            working_reg <= working_next;
        end
    end

    
    wire block_couter_last_clock;
    assign block_couter_last_clock = block_couter_reg == BLOCK_COUNT - 1;

    wire cal_md_counter_last_clock;
    assign cal_md_counter_last_clock = cal_md_counter_reg == 64;

    wire last_clock;
    assign last_clock = (block_couter_last_clock && cal_md_counter_last_clock);
    
    always @(*) begin
        if (working_trig) begin
            working_next = 1;
        end
        else if (last_clock) begin
            working_next = 0;
        end
        else begin
            working_next = working_next;
        end
    end

    always @(*) begin
        if (working_trig) begin
            finish_next = 0;
        end
        else if (finish_reg) begin
            finish_next = 1;     
        end
        else begin
            finish_next = last_clock;
        end
    end

    always @(*) begin
        if (working_trig) begin
            block_couter_next = 0;
        end
        else if (working_reg) begin
            if (cal_md_counter_last_clock) begin
                block_couter_next = block_couter_reg + 1;
            end else begin
                block_couter_next = block_couter_reg;
            end       
        end
        else begin
            block_couter_next = 0;
        end
    end

    always @(*) begin
        if (working_trig) begin
            cal_md_counter_next = 0;
        end
        else if (working_reg) begin
            if (cal_md_counter_last_clock) begin
                cal_md_counter_next = 0;
            end
            else begin
                cal_md_counter_next = cal_md_counter_reg + 1;
            end     
        end
        else begin
            cal_md_counter_next = 0;
        end
    end

    wire is_init;
    wire [6:0] _md_index;
    wire [5:0] md_index;

    assign is_init = cal_md_counter_reg == 0;
    assign _md_index = cal_md_counter_reg - 1;
    assign md_index = _md_index[5:0];

    reg [31:0] t1;
    reg [31:0] t2;
    reg [31:0] s1;
    reg [31:0] s0;
    reg [31:0] ch;
    reg [31:0] maj;
    
    integer i;
    always @(*) begin
        if (working_trig) begin
            for (i = 0; i < 8; i = i + 1) begin
                md_next[i] = H[(7-i)*32+31 -: 32];
            end
            for (i = 0; i < 64; i = i + 1) begin
                w_next[i] = 0;
            end
            a_next = 0;
            b_next = 0;
            c_next = 0;
            d_next = 0;
            e_next = 0;
            f_next = 0;
            g_next = 0;
            h_next = 0;
        end
        else if (working_reg) begin
            if (is_init) begin
                a_next = md_reg[0];
                b_next = md_reg[1];
                c_next = md_reg[2];
                d_next = md_reg[3];
                e_next = md_reg[4];
                f_next = md_reg[5];
                g_next = md_reg[6];
                h_next = md_reg[7];

                for (i = 0; i < 8; i = i + 1) begin
                    md_next[i] = md_reg[i];
                end
                for (i = 0; i < 64; i = i + 1) begin
                    w_next[i] = 0;
                end
            end
            else begin
                for (i = 0; i < 64; i = i + 1) begin
                    w_next[i] = w_reg[i];
                end

                if (md_index < 16) begin
                    w_next[md_index] = block_data[((15-md_index)*32+31) -: 32];
                end
                else begin
                    right_rotate_inputs[0] = w_reg[md_index - 15];
                    right_rotate_inputs[1] = w_reg[md_index - 15];
                    right_rotate_inputs[2] = w_reg[md_index - 2];
                    right_rotate_inputs[3] = w_reg[md_index - 2];
                    
                    w_next[md_index] = 
                        w_reg[md_index - 16] +
                        (right_rotate_outputs[0] ^ right_rotate_outputs[1] ^ (w_reg[md_index - 15] >> 3)) +
                        (right_rotate_outputs[2] ^ right_rotate_outputs[3] ^ (w_reg[md_index - 2] >> 10)) +
                        w_reg[md_index - 7];
                end

                right_rotate_inputs[4] = e_reg;
                right_rotate_inputs[5] = e_reg;
                right_rotate_inputs[6] = e_reg;
                right_rotate_inputs[7] = a_reg;
                right_rotate_inputs[8] = a_reg;
                right_rotate_inputs[9] = a_reg;

                // t1 = 
                //     h_reg + 
                //     (right_rotate_outputs[4] ^ right_rotate_outputs[5] ^ right_rotate_outputs[6]) + 
                //     (e_reg & f_reg) ^ ((~e_reg) & g_reg) +
                //     K[32*md_index+31 -: 32] +
                //     w_next[md_index];


                // t2 = 
                //     (right_rotate_outputs[7] ^ right_rotate_outputs[8] ^ right_rotate_outputs[9]) +
                //     ((a_reg & b_reg) ^ (a_reg & c_reg) ^ (b_reg & c_reg));

                s1 = (right_rotate_outputs[4] ^ right_rotate_outputs[5] ^ right_rotate_outputs[6]);

                ch = (e_reg & f_reg) ^ ((~e_reg) & g_reg);

                t1 = h_reg + s1 + ch + K[32*(63-md_index)+31 -: 32] + w_next[md_index];

                s0 = (right_rotate_outputs[7] ^ right_rotate_outputs[8] ^ right_rotate_outputs[9]);

                maj = ((a_reg & b_reg) ^ (a_reg & c_reg) ^ (b_reg & c_reg));

                t2 = s0 + maj;

                h_next = g_reg;
                g_next = f_reg;
                f_next = e_reg;
                e_next = d_reg + t1;
                d_next = c_reg;
                c_next = b_reg;
                b_next = a_reg;
                a_next = t1 + t2;

                if (cal_md_counter_last_clock) begin
                    md_next[0] = md_reg[0] + a_next;   
                    md_next[1] = md_reg[1] + b_next;   
                    md_next[2] = md_reg[2] + c_next;   
                    md_next[3] = md_reg[3] + d_next;   
                    md_next[4] = md_reg[4] + e_next;   
                    md_next[5] = md_reg[5] + f_next;   
                    md_next[6] = md_reg[6] + g_next;   
                    md_next[7] = md_reg[7] + h_next;   
                end else begin
                    md_next[0] = md_reg[0];
                    md_next[1] = md_reg[1];  
                    md_next[2] = md_reg[2];
                    md_next[3] = md_reg[3];
                    md_next[4] = md_reg[4];
                    md_next[5] = md_reg[5];
                    md_next[6] = md_reg[6];
                    md_next[7] = md_reg[7];
                end
            end
        end
        else begin
            for (i = 0; i < 8; i = i + 1) begin
                md_next[i] = md_reg[i];
            end
            for (i = 0; i < 8; i = i + 1) begin
                w_next[i] = 0;
            end
            a_next = 0;
            b_next = 0;
            c_next = 0;
            d_next = 0;
            e_next = 0;
            f_next = 0;
            g_next = 0;
            h_next = 0;
        end
    end

    assign sha_data = {
        md_reg[0],
        md_reg[1],
        md_reg[2],
        md_reg[3],
        md_reg[4],
        md_reg[5],
        md_reg[6],
        md_reg[7]
    }; 

    assign finish = finish_reg;

    reg  [31:0] right_rotate_inputs [0:9];
    wire [31:0] right_rotate_outputs [0:9];

    right_rotate u0_right_rotate (
        .in_data(right_rotate_inputs[0]),
        .dis(5'd7),
        .out_data(right_rotate_outputs[0])
    );

    right_rotate u1_right_rotate (
        .in_data(right_rotate_inputs[1]),
        .dis(5'd18),
        .out_data(right_rotate_outputs[1])
    );

    right_rotate u2_right_rotate (
        .in_data(right_rotate_inputs[2]),
        .dis(5'd17),
        .out_data(right_rotate_outputs[2])
    );

    right_rotate u3_right_rotate (
        .in_data(right_rotate_inputs[3]),
        .dis(5'd19),
        .out_data(right_rotate_outputs[3])
    );

    right_rotate u4_right_rotate (
        .in_data(right_rotate_inputs[4]),
        .dis(5'd6),
        .out_data(right_rotate_outputs[4])
    );

    right_rotate u5_right_rotate (
        .in_data(right_rotate_inputs[5]),
        .dis(5'd11),
        .out_data(right_rotate_outputs[5])
    );

    right_rotate u6_right_rotate (
        .in_data(right_rotate_inputs[6]),
        .dis(5'd25),
        .out_data(right_rotate_outputs[6])
    );

    right_rotate u7_right_rotate (
        .in_data(right_rotate_inputs[7]),
        .dis(5'd2),
        .out_data(right_rotate_outputs[7])
    );

    right_rotate u8_right_rotate (
        .in_data(right_rotate_inputs[8]),
        .dis(5'd13),
        .out_data(right_rotate_outputs[8])
    );

    right_rotate u9_right_rotate (
        .in_data(right_rotate_inputs[9]),
        .dis(5'd22),
        .out_data(right_rotate_outputs[9])
    );

endmodule